
/*
 *  Copyright t lefering, David Given
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  These are the four essential freedoms with GNU GPL software:
 *  1: freedom to run the program, for any purpose
 *  2: freedom to study how the program works, and change it to make it do what you wish
 *  3: freedom to redistribute copies to help your Free Software friends
 *  4: freedom to distribute copies of your modified versions to your Free Software friends
 *   ,           ,
 *  /             \
 * ((__-^^-,-^^-__))
 * `-_---'  `---_-'
 *  `--|o`   'o|--'
 *      \  `  /
 *       ): :(
 *       :o_o:
 *        "-"
 */

#include "globals.h"

static FILE *f = NULL;

static void sparse_output_bb(struct basic_block *bb, const char *fname, const char *sname)
{
	struct instruction *insn = NULL;
	const char *is = NULL;
	int c = 0;
	const char *p = NULL;
	char *q = NULL;
	char *buf = NULL;
	int cl = 0;
	fprintf(f, "%s() line %d file %s\\lbasic block number %d\\l%s\\l", fname, bb->pos.line, sname, bb->nr, show_label(bb));

	FOR_EACH_PTR(bb->insns, insn) {
		if (!insn->bb) {
			continue;
		}
		is = show_instruction(insn);
		/* the instruction can have "" to set as escape */
		if (strchr(is, '"')) {
			/* how many " in s */
			c = 0;
			p = is;
			while (*p) {
				if ((*p) == '"') {
					c++;
				}
				p++;
			}
			/* how many space for new string */
			cl = strlen(is) + 1 + (2 * c);
			buf = calloc(1, cl);
			p = is;
			q = buf;
			/* change " into \" */
			/* change \ into \\ */
			while (*p) {
				if ((*p) == '"') {
					*q = '\\';
					q++;
					*q = '"';
				} else if ((*p) == '\\') {
					*q = '\\';
					q++;
					*q = '\\';
				} else {
					*q = *p;
				}
				q++;
				p++;
			}
			is = buf;
		} else {
			/* no changes needed */
		}
		fprintf(f, "  %s\\l", is);
		if (buf) {
			free(buf);
			buf = NULL;
		}
	}
	END_FOR_EACH_PTR(insn);

	return;
}

/* Draw the subgraph for a given entrypoint. Includes details of loads
 * and stores for globals, and marks return bbs */
static void graph_ep(struct entrypoint *ep)
{
	struct basic_block *bb;
	struct instruction *insn;
	const char *fname, *sname;

	fname = show_ident(ep->name->ident);
	sname = stream_name(ep->entry->bb->pos.stream);

	fprintf(f, "subgraph cluster%p {\n"
		"    color=blue;\n"
		"    label=\"%s\\l%s()\\l"
		"    file=%s;\\l" "    fun=%s;\\l" "    ep=bb%p;\\l\"\n", ep, sname, fname, sname, fname, ep->entry->bb);

	FOR_EACH_PTR(ep->bbs, bb) {
		struct basic_block *child;
		int ret = 0;
		const char *s = ", ls=\"[";

		/* Node for the bb */
		fprintf(f, "    bb%p [shape=ellipse,label=\"", bb);

		sparse_output_bb(bb, fname, sname);

		fprintf(f, "\",line=%d,col=%d", bb->pos.line, bb->pos.pos);

		/* List loads and stores */
		FOR_EACH_PTR(bb->insns, insn) {
			if (!insn->bb)
				continue;
			switch (insn->opcode) {
			case OP_STORE:
				if (insn->src->type == PSEUDO_SYM) {
					fprintf(f, "%s store(%s)", s, show_ident(insn->src->sym->ident));
					s = ",";
				}
				break;

			case OP_LOAD:
				if (insn->src->type == PSEUDO_SYM) {
					fprintf(f, "%s load(%s)", s, show_ident(insn->src->sym->ident));
					s = ",";
				}
				break;

			case OP_RET:
				ret = 1;
				break;

			}
		}
		END_FOR_EACH_PTR(insn);
		if (s[1] == 0)
			fprintf(f, "]\"");
		if (ret)
			fprintf(f, ",op=ret");
		fprintf(f, "];\n");

		/* Edges between bbs; lower weight for upward edges */
		FOR_EACH_PTR(bb->children, child) {
			fprintf(f, "    bb%p -> bb%p [op=br, %s];\n", bb, child,
				(bb->pos.line > child->pos.line) ? "weight=5" : "weight=10");
		}
		END_FOR_EACH_PTR(child);
	}
	END_FOR_EACH_PTR(bb);

	fprintf(f, "}\n");
}

/* Insert edges for intra- or inter-file calls, depending on the value
 * of internal. Bold edges are used for calls with destinations;
 * dashed for calls to external functions */
static void graph_calls(struct entrypoint *ep, int internal)
{
	struct basic_block *bb;
	struct instruction *insn;

	show_ident(ep->name->ident);
	stream_name(ep->entry->bb->pos.stream);

	FOR_EACH_PTR(ep->bbs, bb) {
		if (!bb)
			continue;
		if (!bb->parents && !bb->children && !bb->insns && verbose < 2)
			continue;

		FOR_EACH_PTR(bb->insns, insn) {
			if (!insn->bb)
				continue;
			if (insn->opcode == OP_CALL && internal == !(insn->func->sym->ctype.modifiers & MOD_EXTERN)) {

				/* Find the symbol for the callee's definition */
				struct symbol *sym;
				if (insn->func->type == PSEUDO_SYM) {
					for (sym = insn->func->sym->ident->symbols; sym; sym = sym->next_id) {
						if (sym->namespace & NS_SYMBOL && sym->ep)
							break;
					}

					if (sym)
						fprintf(f, "bb%p -> bb%p"
							"[label=\"line\\l%d\",line=%d,col=%d,op=call,style=bold,weight=30];\n",
							bb, sym->ep->entry->bb, insn->pos.line, insn->pos.line, insn->pos.pos);
					else
						fprintf(f, "bb%p -> \"%s\" "
							"[label=\"line\\l%d\",line=%d,col=%d,op=extern,style=dashed];\n",
							bb, show_pseudo(insn->func), insn->pos.line, insn->pos.line, insn->pos.pos);
				}
			}
		}
		END_FOR_EACH_PTR(insn);
	}
	END_FOR_EACH_PTR(bb);
}

void graph(void)
{
	struct symbol *sym = NULL;

	f = fopen("output.gv", "w");
	if (!f) {
		return;
	}
	fprintf(f, "/* generated with sparsegen */\ndigraph sparsegen {\n");
	FOR_EACH_PTR(all_syms, sym) {
		expand_symbol(sym);
		if (0) {
			/* this has already be done */
			linearize_symbol(sym);
		}
	}
	END_FOR_EACH_PTR(sym);

	FOR_EACH_PTR(all_syms, sym) {
		if (sym->ep) {
			graph_ep(sym->ep);
			graph_calls(sym->ep, 1);
		}
	}
	END_FOR_EACH_PTR(sym);

	/* Graph inter-file calls */
	FOR_EACH_PTR(all_syms, sym) {
		if (sym->ep)
			graph_calls(sym->ep, 0);
	}
	END_FOR_EACH_PTR(sym);

	fprintf(f, "}\n");
	fclose(f);
	return;
}

/* end */
